using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class Grid<T> : IEnumerable<T> {
	private T[,] m_gridArray;
	public Vector2Int size => new Vector2Int(m_gridArray.GetLength(0), m_gridArray.GetLength(1));

	public Grid(int width, int height) {
		m_gridArray = new T[width, height];
	}

	public T GetValue(int x, int y) {
		if (x >= 0 && y >= 0 && x < m_gridArray.GetLength(0) && y < m_gridArray.GetLength(1)) {
			return m_gridArray[x, y];
		}
		return default(T);
	}

	public void SetValue(int x, int y, T value) {
		if (x >= 0 && y >= 0 && x < m_gridArray.GetLength(0) && y < m_gridArray.GetLength(1)) {
			m_gridArray[x, y] = value;
		}
	}

	public bool IsMistIdxLegal(int x, int y) {
		return x >= 1 && x < size.x - 1 && y >= 1 && y < size.y - 1;
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator() {
		for (int i = 0; i < m_gridArray.GetLength(0); i++) {
			for (int j = 0; j < m_gridArray.GetLength(1); j++) {
				yield return m_gridArray[i, j];
			}
		}
	}
	public IEnumerator GetEnumerator() {
		return m_gridArray.GetEnumerator();
	}
}

public class TilemapDataManager : MonoSingleton<TilemapDataManager> {
	public Tilemap tilemap;
	private Grid<CustomTileData> m_gridData;

	public Vector2Int Size => m_gridData.size;

	public List<Sprite> NumsSprites;

	public void LoadMap(Tilemap tilemap, Vector2Int size) {
		this.tilemap = tilemap;
		var bounds = tilemap.cellBounds;
		m_gridData = new Grid<CustomTileData>(size.x, size.y);
		foreach (var pos in bounds.allPositionsWithin) {
			var localPos = pos - bounds.min;
			var isWall = localPos.x == 0 || localPos.y == 0 || localPos.x >= size.x - 1 || localPos.y >= size.y - 1;
			m_gridData.SetValue(localPos.x, localPos.y, new CustomTileData() {
				isWall = isWall,
			});
		}

		var entityMap = tilemap.transform.parent.GetComponentsInChildren<Tilemap>()[2];
		foreach (var gridEntity in entityMap.transform.GetComponentsInChildren<GridEntity>()) {
			var tilePos = GetTilePosition(gridEntity.transform.position);
			m_gridData.SetValue(tilePos.x, tilePos.y, new CustomTileData { entityTag = gridEntity.entityTag, entity = gridEntity});
		}

		UpdateMistTilemap();
		InitStoneNums();
	}

	private void InitStoneNums()
	{
		
		for (int i = 0; i < m_gridData.size.x; i++)
		{
			for (int j = 0; j < m_gridData.size.y; j++)
			{
				UpdateStoneNum(new Vector2Int(i, j));
			}
		}
	}

	private void UpdateStoneNum(Vector2Int idx)
	{
		if (!m_gridData.IsMistIdxLegal(idx.x, idx.y))
		{
			return;
		}
		
		int checkIfIsSpecialRock(int x, int y)
		{
			if (m_gridData.IsMistIdxLegal(x, y))
			{
				var entity = m_gridData.GetValue(x, y);
				if (entity.entityTag != null 
				    && entity.entityTag.EndsWith("stone") 
				    && entity.entityTag != "stone")
				{
					return 1;
				}
				else
				{
					return 0;
				}
			}
			return 0;
		}

		int i = idx.x;
		int j = idx.y;
		
		var entity = m_gridData.GetValue(i, j);
		if (entity.entityTag == "stone")
		{
			// find nearest 
			int cnt = 0;
			cnt += checkIfIsSpecialRock(i - 1, j - 1);
			cnt += checkIfIsSpecialRock(i - 1, j);
			cnt += checkIfIsSpecialRock(i - 1, j + 1);
			cnt += checkIfIsSpecialRock(i, j + 1);
			cnt += checkIfIsSpecialRock(i + 1, j + 1);
			cnt += checkIfIsSpecialRock(i + 1, j);
			cnt += checkIfIsSpecialRock(i + 1, j - 1);
			cnt += checkIfIsSpecialRock(i, j - 1);

			if (entity.entity.stoneNumSpr)
			{
				if (cnt > 0)
				{
					entity.entity.stoneNumSpr.sprite = NumsSprites[cnt];
				}
				else
				{
					entity.entity.stoneNumSpr.sprite = null;
				}
			}
			
		}
	}

	private void MinusAllNearbyStoneCounts(Vector2Int idx)
	{
		void minusPos(int x, int y)
		{
			if (!m_gridData.IsMistIdxLegal(x, y)) { return; }

			var entity = m_gridData.GetValue(x, y);
			if (entity.entity == null
				|| entity.entity.stoneNumSpr == null 
			    || entity.entity.stoneNumSpr.sprite == null) { return; }

			int newSpriteNum = NumsSprites.FindIndex(e => e == entity.entity.stoneNumSpr.sprite) - 1;

			if (newSpriteNum > 0)
			{
				entity.entity.stoneNumSpr.sprite = NumsSprites[newSpriteNum];
			}
			else
			{
				entity.entity.stoneNumSpr.sprite = null;
			}
			
		}

		int i = idx.x;
		int j = idx.y;
		minusPos(i - 1, j - 1);
		minusPos(i - 1, j);
		minusPos(i - 1, j + 1);
		minusPos(i, j + 1);
		minusPos(i + 1, j + 1);
		minusPos(i + 1, j);
		minusPos(i + 1, j - 1);
		minusPos(i, j - 1);
	}
	

	public void SetData(Vector3Int position, CustomTileData data) {
		var localPosition = position - tilemap.cellBounds.min;

		var originData = m_gridData.GetValue(localPosition.x, localPosition.y);
		
		m_gridData.SetValue(localPosition.x, localPosition.y, data);

		UpdateMistTilemap();

		if (originData != null 
		    && originData.entityTag != null
		    && originData.entityTag.EndsWith("stone") 
		    && originData.entityTag != "stone")
		{
			MinusAllNearbyStoneCounts(new Vector2Int(localPosition.x, localPosition.y));	
		}
	}

	public void SetData(Vector3 worldPos, CustomTileData data) {
		var position = tilemap.layoutGrid.WorldToCell(worldPos);
		SetData(position, data);
	}

	public CustomTileData GetData(Vector3Int position) {
		var localPosition = position - tilemap.cellBounds.min;
		return m_gridData.GetValue(localPosition.x, localPosition.y);
	}

	List<Vector3Int> buffer = new();
	private Vector3Int[] dirs = new Vector3Int[] {
		new Vector3Int(1,0,0),
		new Vector3Int(-1,0,0),
		new Vector3Int(0,1,0),
		new Vector3Int(0,-1,0),
	};
	public Vector3Int GetMon3trWalkableDir(Vector3Int position) {
		buffer.Clear();
		for (int i = 0; i < 4; i++) {
			var dir = dirs[i];
			var findingPlayer = true;
			var addFallBack = false;

			var cell = position;
			var playerTile = GetTilePosition(PlayerEntity.Instance.transform.position);

			while (findingPlayer) {
				cell = cell + dir;
				var data = m_gridData.GetValue(cell.x, cell.y);
				if (data == null) {
					break;
				}
				if (data.isWall || IsObstacle(data)) {
					break;
				}
				if (!addFallBack) {
					addFallBack = true;
					buffer.Add(dir);
				}
				if (cell == playerTile) {
					Debug.Log("found player");
					return dir;
				}
			}
		}
		if (buffer.Count == 0) {
			return Vector3Int.zero;
		}
		var idx = Random.Range(0, buffer.Count);
		return buffer[idx];
	}

	public bool IsMon3trWalkable(Vector3Int position) {
		var data = m_gridData.GetValue(position.x, position.y);
		if (data == null) {
			return false;
		}
		return !IsObstacle(data);
	}

	public Vector3Int GetTilePosition(Vector3 worldPos) {
		var position = tilemap.layoutGrid.WorldToCell(worldPos);
		var localPosition = position - tilemap.cellBounds.min;
		return new Vector3Int(localPosition.x, localPosition.y, 0);
	}

	public Vector3Int GetTilePosition(Vector3Int gridIdx) {
		var bounds = tilemap.cellBounds;
		return gridIdx + bounds.min;
	}

	public Vector3 GetWorldPosition(Vector3Int tilePos) {
		tilePos += tilemap.cellBounds.min;
		return tilemap.layoutGrid.CellToWorld(tilePos) + tilemap.tileAnchor;
	}

	private void UpdateMistTilemap() {
		var mistMap = tilemap.transform.parent.GetComponentsInChildren<Tilemap>()[3];

		// find spawn
		Vector2Int spawnIdx = Vector2Int.zero;
		for (int i = 0; i < m_gridData.size.x; i++) {
			for (int j = 0; j < m_gridData.size.y; j++) {
				if (m_gridData.GetValue(i, j).entityTag == "spawn") {
					spawnIdx = new Vector2Int(i, j);
					SetMist(mistMap, new Vector2Int(i, j), false);
				}
				if (m_gridData.GetValue(i, j).entityTag == "pit") {
					SetMist(mistMap, new Vector2Int(i, j), false);
				}
			}
		}

		// create mist grid
		bool[,] visitedGrid = new bool[m_gridData.size.x, m_gridData.size.y];
		for (int i = 0; i < visitedGrid.GetLength(0); i++) {
			for (int j = 0; j < visitedGrid.GetLength(1); j++) {
				visitedGrid[i, j] = false;
			}
		}

		Queue<Vector2Int> que = new Queue<Vector2Int>();
		que.Enqueue(spawnIdx);

		void bfs(int x, int y) {
			if (m_gridData.IsMistIdxLegal(x, y) && !visitedGrid[x, y]) {
				// disable mist
				SetMist(mistMap, new Vector2Int(x, y), false);

				if (!IsObstacle(m_gridData.GetValue(x, y))) {
					que.Enqueue(new Vector2Int(x, y));
				}
			}
		}

		while (que.Count != 0) {
			var cur = que.Peek();
			que.Dequeue();
			visitedGrid[cur.x, cur.y] = true;

			bfs(cur.x - 1, cur.y);
			bfs(cur.x + 1, cur.y);
			bfs(cur.x, cur.y - 1);
			bfs(cur.x, cur.y + 1);

		}
	}

	private void SetMist(Tilemap mistMap, Vector2Int idx, bool on) {

		if (!on) {
			mistMap.SetTile(GetTilePosition(new Vector3Int(idx.x, idx.y, 0)), null);
		}
	}

	private bool IsObstacle(CustomTileData data) {
		return data.isWall || (data.entityTag != null && data.entityTag.EndsWith("stone"));
	}
}
